﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="BubbleChart.cs" company="">
//   
// </copyright>
// <summary>
//   The candlestick chart.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Linq;

using Graphix.Elements;
using Graphix.Helpers;

namespace Graphix.Charts
{
    using CommonHelpers;
    using Graphix.Charts.Base;
    using Graphix.Charts.Values;

    /// <summary>
    ///   The candlestick chart.
    /// </summary>
    public class BubbleChart : BothAxisAndLegend<Bubble>
    {
        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="BubbleChart"/> class.
        /// </summary>
        public BubbleChart()
        {
            this.SetsName = new Dictionary<string, Color>();
            this.BubbleStyle = new BubbleStyle();
            this.BubbleSizeAxis = new BubbleSizeAxis();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the bubble size axis.
        /// </summary>
        public BubbleSizeAxis BubbleSizeAxis { get; set; }

        /// <summary>
        /// Gets or sets the bubble style.
        /// </summary>
        public BubbleStyle BubbleStyle { get; set; }
        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the sets name.
        /// </summary>
        protected Dictionary<string, Color> SetsName { get; set; }

        public string[] Labels { get; set; }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The add series.
        /// </summary>
        /// <param name="values">
        /// The values.
        /// </param>
        public void AddSeries(string name, List<Bubble> values)
        {
            Color usedColor = this.GetNextColor();
            if (!this.SetsName.ContainsKey(name))
            {
                this.SetsName.Add(name, usedColor);
            }

            this.values.Add(new ValueSet<Bubble>(usedColor, name, values));
        }

        #endregion

        #region Methods

        /// <summary>
        /// The add bars.
        /// </summary>
        protected void AddVisibleData()
        {
            for (int i = 0; i < this.values.Count; i++)
            {
                ValueSet<Bubble> information = this.values[i];
                for (int j = 0; j < information.Values.Count; j++)
                {
                    Bubble value = information.Values[j];
                    float radius = (value.Size - this.BubbleSizeAxis.MinimumValue)
                                   / (this.BubbleSizeAxis.MaximumValue - this.BubbleSizeAxis.MinimumValue);

                    radius *= this.BubbleSizeAxis.MaximumSize;

                    if (radius > this.BubbleSizeAxis.MaximumSize)
                    {
                        radius = this.BubbleSizeAxis.MaximumSize;
                    }
                    else if (radius < this.BubbleSizeAxis.MinimumSize)
                    {
                        radius = this.BubbleSizeAxis.MinimumSize;
                    }

                    this.DrawPoint(value.X, value.Y, this.SetsName[information.Name], radius, value.Size, value.Name, i);
                }
            }
        }

        /// <summary>
        /// The draw chart.
        /// </summary>
        protected override void DrawChart()
        {
            if (this.DisplayLegend)
            {
                this.BuildLegend(this.SetsName.Select(x => x.Key).ToList(), this.values);
            }

            this.DrawTitle();
            this.AddYAxisTitle();
            this.AddXAxisTitle();

            this.AddTicks();

            this.AddVisibleData();
        }

        /// <summary>
        /// The add ticks.
        /// </summary>
        private void AddTicks()
        {
            if (this.YAxis.DisplayValues)
            {
                string longest = this.YAxis.Scale.NiceMin.ToString(CultureInfo.InvariantCulture);
                if (this.YAxis.Scale.NiceMax.ToString(CultureInfo.InvariantCulture).Length
                    > longest.Length)
                {
                    longest = this.YAxis.Scale.NiceMax.ToString(CultureInfo.InvariantCulture);
                }

                var measured = DefaultGraphics.Instance.MeasureDisplayString(longest, this.YAxis.TextStyle.Font);
                this.Border.Left += measured.Height;
            }

            float ticks = this.XAxis.Scale.Range / this.YAxis.Scale.TickSpacing;
            float tickSize = this.Border.Width / ticks;
            float top = this.Border.Top;

            if (this.XAxis.DisplayValues)
            {
                this.Border.Bottom -= DefaultGraphics.Instance.MeasureDisplayString("|", this.XAxis.TextStyle.Font).Height;
            }

            float bottom = this.Border.Bottom;

            float left = this.Border.Left;
            float right = this.Border.Right;

            for (int i = 0; i <= ticks; i++)
            {
                float current;
                if (this.XAxis.OppositeDirection)
                {
                    current = left + (tickSize * (ticks - i));
                }
                else
                {
                    current = left + (tickSize * i);
                }

                this.AddElement(new Line(current, top, current, bottom, this.XAxis.MajorColor)
                    {
                        MoveWhen = PerformMove.Left | PerformMove.Right
                    });

                if (this.XAxis.DisplayValues)
                {
                    var alignment = StringAlignment.Center;
                    if (this.XAxis.OppositeDirection)
                    {
                        if (i >= ticks)
                        {
                            alignment = StringAlignment.Near;
                        }
                        else if (i == 0)
                        {
                            alignment = StringAlignment.Far;
                        }
                    }
                    else
                    {
                        if (i == 0)
                        {
                            alignment = StringAlignment.Near;
                        }
                        else if (i >= ticks)
                        {
                            alignment = StringAlignment.Far;
                        }
                    }

                    string text = (this.XAxis.Scale.NiceMin + (this.XAxis.Scale.TickSpacing * i)).
                        ToString(CultureInfo.InvariantCulture);
                    text = string.Format(this.XAxis.Format, text);

                    this.AddElement(new ChartString(current, this.Border.Bottom, 0, 0,
                        text,
                        this.XAxis.TextStyle.Font)
                        {
                            Format = new StringFormat { Alignment = alignment },
                            MoveWhen = PerformMove.Left | PerformMove.Right,
                            Color = this.XAxis.TextStyle.ColorBrush
                        });
                }
            }

            ticks = this.YAxis.Scale.Range / this.YAxis.Scale.TickSpacing;
            tickSize = this.Border.Height / ticks;
            for (int i = 0; i <= ticks; i++)
            {
                float current;
                if (this.YAxis.OppositeDirection)
                {
                    current = bottom - (tickSize * (ticks - i));
                }
                else
                {
                    current = bottom - (tickSize * i);
                }

                this.AddElement(new Line(left, current, right, current, this.YAxis.MajorColor)
                    {
                        MoveWhen = PerformMove.Left | PerformMove.Right
                    });

                if (this.YAxis.DisplayValues)
                {
                    var alignment = StringAlignment.Center;
                    if (this.YAxis.OppositeDirection)
                    {
                        if (i >= ticks)
                        {
                            alignment = StringAlignment.Far;
                        }
                        else if (i == 0)
                        {
                            alignment = StringAlignment.Near;
                        }
                    }
                    else
                    {
                        if (i == 0)
                        {
                            alignment = StringAlignment.Far;
                        }
                        else if (i >= ticks)
                        {
                            alignment = StringAlignment.Near;
                        }
                    }

                    string text = (this.YAxis.Scale.NiceMin + (this.YAxis.Scale.TickSpacing * i)).
                        ToString(CultureInfo.InvariantCulture);
                    text = string.Format(this.YAxis.Format, text);

                    this.AddElement(new ChartString(this.Border.Left - 2, current, 0, 0,
                        text,
                        this.YAxis.TextStyle.Font)
                        {
                            Format =
                                new StringFormat
                                    {
                                        LineAlignment = alignment,
                                        Alignment = StringAlignment.Far
                                    },
                            MoveWhen = PerformMove.Left | PerformMove.Right,
                            Color = this.YAxis.TextStyle.ColorBrush
                        });
                }
            }
        }

        /// <summary>
        /// The draw point.
        /// </summary>
        /// <param name="x">
        /// The x.
        /// </param>
        /// <param name="y">
        /// The y.
        /// </param>
        /// <param name="color">
        /// The color.
        /// </param>
        /// <param name="size">
        /// The size.
        /// </param>
        /// <param name="text">
        /// The text.
        /// </param>
        private void DrawPoint(float x, float y, Color color, float size, float realSize, string text, int group)
        {
            if (size < this.BubbleSizeAxis.MinimumSize)
            {
                size = this.BubbleSizeAxis.MinimumSize;
            }
            else if (size > this.BubbleSizeAxis.MaximumSize)
            {
                size = this.BubbleSizeAxis.MaximumSize;
            }
            float yPosition;
            if (this.YAxis.OppositeDirection)
            {
                yPosition = Math.Abs(this.YAxis.Scale.NiceMin)
                            + Math.Abs(this.YAxis.Scale.NiceMax);
                yPosition = 1 - ((y + Math.Abs(this.YAxis.Scale.NiceMin)) / yPosition);
                yPosition = this.Border.Height - (yPosition * this.Border.Height) + this.Border.Top;
            }
            else
            {
                yPosition = Math.Abs(this.YAxis.Scale.NiceMin)
                            + Math.Abs(this.YAxis.Scale.NiceMax);
                yPosition = (y + Math.Abs(this.YAxis.Scale.NiceMin)) / yPosition;
                yPosition = this.Border.Height - (yPosition * this.Border.Height) + this.Border.Top;
            }

            float xPosition;
            if (this.XAxis.OppositeDirection)
            {
                xPosition = Math.Abs(this.XAxis.Scale.NiceMin)
                            + Math.Abs(this.XAxis.Scale.NiceMax);
                xPosition = 1 - ((x + Math.Abs(this.XAxis.Scale.NiceMin)) / xPosition);
                xPosition = (xPosition * this.Border.Width) + this.Border.Left;
            }
            else
            {
                xPosition = Math.Abs(this.XAxis.Scale.NiceMin)
                            + Math.Abs(this.XAxis.Scale.NiceMax);
                xPosition = (x + Math.Abs(this.XAxis.Scale.NiceMin)) / xPosition;
                xPosition = (xPosition * this.Border.Width) + this.Border.Left;
            }

            float half = size / 2f;
            Dot point = new Dot(xPosition - half, yPosition - half, size, new SolidBrush(color))
                {
                    BorderColor = this.BubbleStyle.Border,
                };
            string data = string.Format("l=;v=[\"{0}\", \"{1}\", \"{2}\", \"{3}\"]", text, x, y, realSize);
            point.Attributes["g"] = group;
            point.Attributes["data-ot"] = data;
            this.AddElement(point);

            var element = new ChartString(xPosition, yPosition, 0, 0, text,
                this.BubbleStyle.TextStyle.Font)
                {
                    Format =
                        new StringFormat
                            {
                                LineAlignment = StringAlignment.Center,
                                Alignment = StringAlignment.Center
                            },
                    Color = new SolidBrush(this.BubbleStyle.TextStyle.Color)
                };
            element.Attributes["g"] = group;
            element.Attributes["data-ot"] = data;
            this.AddElement(element);
        }

        protected override string JavascriptLabelsValues()
        {
            if (this.Labels != null)
            {
                var temp = new ArraySegment<string>(this.Labels, 0, 4);
                return string.Join(",", temp.Select(x => string.Format("\"{0}\"", x)));
            }

            return string.Empty;
        }

        /// <summary>
        /// The pre draw.
        /// </summary>
        protected override void PreDraw()
        {
            this.BubbleStyle.TextStyle = this.SetupFont(this.BubbleStyle.TextStyle);

            if (float.IsNaN(this.BubbleSizeAxis.MinimumValue))
            {
                this.BubbleSizeAxis.MinimumValue = this.values.Min(x => x.Values.Min(y => y.Size));
            }

            if (float.IsNaN(this.BubbleSizeAxis.MaximumValue))
            {
                this.BubbleSizeAxis.MaximumValue = this.values.Max(x => x.Values.Max(y => y.Size));
            }

            // X
            if (float.IsNaN(this.XAxis.Minimum))
            {
                this.XAxis.Minimum = this.values.Min(x => x.Values.Min(y => y.X));
            }

            if (float.IsNaN(this.XAxis.Maximum))
            {
                this.XAxis.Maximum = this.values.Max(x => x.Values.Max(y => y.X));
            }

            // Y
            if (float.IsNaN(this.YAxis.Minimum))
            {
                this.YAxis.Minimum = this.values.Min(x => x.Values.Min(y => y.Y));
            }

            if (float.IsNaN(this.YAxis.Maximum))
            {
                this.YAxis.Maximum = this.values.Max(x => x.Values.Max(y => y.Y));
            }

            this.XAxis.CalculateScale();
            this.YAxis.CalculateScale();
        }

        #endregion
    }
}